﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SmartRoute.Data;
using SmartRoute.BingMaps.GeocodeService;
using SmartRoute.BingMaps.ImageryService;
using SmartRoute.BingMaps.RouteService;
using SmartRoute.BingMaps.SearchService;
using System.Text.RegularExpressions;

namespace SmartRoute.BingMaps
{
    public class RoutingServices : IRoutingService
    {
        /// <summary>
        /// The key used to access BingMap functions.
        /// </summary>
        private const string Key = "AhqjOSdmQlNyiAfMcr8HicreBdxgTnuVaiMGVFnP9x3l_12WQgLyV1pRrfIpECX5";

        public RoutingServices(string key)
        {

        }


        //Returns a route going from source to location
        public Route FromTo(SmartRoute.Data.Location source, SmartRoute.Data.Location destination)
        {
            TimeSpan travelTime;
            double distance;
            SmartRoute.Data.Location[] waypoints;

            SmartRoute.BingMaps.RouteService.GeocodeLocation sourceLocation = new SmartRoute.BingMaps.RouteService.GeocodeLocation();
            sourceLocation.Latitude = source.Latitude;
            sourceLocation.Longitude = source.Longitude;

            SmartRoute.BingMaps.RouteService.GeocodeLocation destLocation = new SmartRoute.BingMaps.RouteService.GeocodeLocation();
            destLocation.Latitude = destination.Latitude;
            destLocation.Longitude = destination.Longitude;

            RouteResponse response = RequestRoute(sourceLocation, destLocation);
            long time = response.Result.Summary.TimeInSeconds;
            travelTime = new TimeSpan(0,0,(int)time);
            distance = response.Result.Summary.Distance;

            //extract waypoints
            waypoints = new SmartRoute.Data.Location[response.Result.RoutePath.Points.Length];
            for (int i = 0; i < response.Result.RoutePath.Points.Length; i++)
            {
                waypoints[i] = SmartRoute.Data.Location.WithoutAddress(
                 response.Result.RoutePath.Points[i].Latitude,response.Result.RoutePath.Points[i].Longitude);
            }
            string directions = PrintRoute(response);

            Route temp = new Route((IRoutingService)this, source, destination, travelTime, distance, waypoints, directions);
            return temp;
        }


        /// <summary>
        /// The following method obtained and modified from http://msdn.microsoft.com/en-us/library/dd221354.aspx
        ///     Gets a GeocodeAddress of an address.
        /// </summary>
        /// <param name="address">The address of a stop</param>
        /// <returns></returns>
        private static SmartRoute.BingMaps.RouteService.GeocodeLocation GeocodeAddress(string address)
        {
            GeocodeRequest geocodeRequest = new GeocodeRequest();

            // Set the credentials using a valid Bing Maps key
            geocodeRequest.Credentials = new GeocodeService.Credentials();
            geocodeRequest.Credentials.ApplicationId = Key;

            // Set the full address query
            geocodeRequest.Query = address;

            // Set the options to only return high confidence results 
            ConfidenceFilter[] filters = new ConfidenceFilter[1];
            filters[0] = new ConfidenceFilter();
            filters[0].MinimumConfidence = GeocodeService.Confidence.High;

            // Add the filters to the options
            GeocodeOptions geocodeOptions = new GeocodeOptions();
            geocodeOptions.Filters = filters;
            geocodeRequest.Options = geocodeOptions;

            // Make the geocode request
            GeocodeServiceClient geocodeService = new GeocodeServiceClient("BasicHttpBinding_IGeocodeService");
            GeocodeResponse geocodeResponse = geocodeService.Geocode(geocodeRequest);
            // make a location object from the response
            SmartRoute.BingMaps.RouteService.GeocodeLocation results = new SmartRoute.BingMaps.RouteService.GeocodeLocation();
            results.Latitude = geocodeResponse.Results[0].Locations[0].Latitude;
            results.Longitude = geocodeResponse.Results[0].Locations[0].Longitude;
            return results;
        }

        /// <summary>
        /// The following method obtained and modified from http://msdn.microsoft.com/en-us/library/dd221354.aspx
        ///     Gets a route from one stop to another.
        /// </summary>
        /// <param name="firstStop">The first stop in the route</param>
        /// <param name="lastStop">The last stop in the route</param>
        /// <returns>RouteResponse object</returns>
        private static RouteResponse RequestRoute(SmartRoute.BingMaps.RouteService.GeocodeLocation firstStop, SmartRoute.BingMaps.RouteService.GeocodeLocation lastStop)
        {
            RouteRequest routeRequest = new RouteRequest();

            // enter bing maps key
            routeRequest.Credentials = new RouteService.Credentials();
            routeRequest.Credentials.ApplicationId = Key;

            //waypoint array for beginning and end of route
            Waypoint[] waypoints = new Waypoint[2];
            waypoints[0] = new Waypoint();
            waypoints[0].Location = firstStop;
            waypoints[0].Description = "Start";

            //waypoint array for beginning and end of route
            waypoints[1] = new Waypoint();
            waypoints[1].Location = lastStop;
            waypoints[1].Description = "End";


            routeRequest.Options = new RouteOptions();
            routeRequest.Options.RoutePathType = RoutePathType.Points;


            routeRequest.Waypoints = waypoints;

            

            // Make the calculate route request
            RouteServiceClient routeService = new RouteServiceClient("BasicHttpBinding_IRouteService");

            RouteResponse routeResponse = routeService.CalculateRoute(routeRequest);

            return routeResponse;

        }
        //this method was found on http://msdn.microsoft.com/en-us/library/dd221354.aspx
        private static string PrintRoute(RouteResponse route)
        {
            string results = "";
            RouteResponse routeResponse = route;

            // Iterate through each itinerary item to get the route directions
            StringBuilder directions = new StringBuilder("");

            if (routeResponse.Result.Legs.Length > 0)
            {
                int instructionCount = 0;
                int legCount = 0;

                foreach (RouteLeg leg in routeResponse.Result.Legs)
                {
                    legCount++;
                    directions.Append(string.Format("Next Leg:\n", legCount));

                    foreach (ItineraryItem item in leg.Itinerary)
                    {
                        instructionCount++;
                        directions.Append(string.Format("{0}. {1}\n",
                            instructionCount, item.Text));
                    }
                }
                //Remove all Bing Maps tags around keywords.  
                //If you wanted to format the results, you could use the tags
                Regex regex = new Regex("<[/a-zA-Z:]*>",
                  RegexOptions.IgnoreCase | RegexOptions.Multiline);
                results = regex.Replace(directions.ToString(), string.Empty);
            }
            else
                results = "No Route found";

            return results;
        }
        private static IEnumerable<SmartRoute.Data.Location> searchLocation(string address){
        
                String key = Key;
                SearchRequest searchRequest = new SearchRequest();

                // Set the credentials using a valid Bing Maps key
                searchRequest.Credentials = new SearchService.Credentials();
                searchRequest.Credentials.ApplicationId = key;
                //Create the search query
                StructuredSearchQuery ssQuery = new StructuredSearchQuery();
                string[] parts = address.Split(',');
                ssQuery.Keyword = parts[0];
                ssQuery.Location = parts[1];
                searchRequest.StructuredQuery = ssQuery;

                //Define options on the search
               /* searchRequest.SearchOptions = new SearchOptions();
                searchRequest.SearchOptions.Filters =
                    new FilterExpression()
                    {
                        PropertyId = 3,
                        CompareOperator = CompareOperator.GreaterThanOrEquals,
                        FilterValue = 8.16
                    };
                * */

                //Make the search request 
                SearchServiceClient searchService = new SearchServiceClient("BasicHttpBinding_ISearchService");
                SearchResponse searchResponse = searchService.Search(searchRequest);
                
                //I changed this, here is the rationale:
                //* Enumerates through all results.
                //* Does not fail if any array is empty.
                //* "Lazier" than returning a list. IE until you actually get the values, this loop doesn't run at all.
                //If there are too many results, you can always filter through it later
                //If you want to do the IO immediately, that probably isn't a problem because you already did the search
                // before reaching that point, but you can always use "ToList" or "ToArray" on the result.
                foreach (var resultSet in searchResponse.ResultSets)
                {
                    foreach (var result in resultSet.Results)
                    {
                        foreach (var place in result.LocationData.Locations)
                            yield return new SmartRoute.Data.Location(place.Latitude, place.Longitude, result.Name);
                    }
                }

                /*
                List<SmartRoute.Data.Location> results = new List<SmartRoute.Data.Location>();

                for (int i = 0; i < searchResponse.ResultSets.Length; i++)
                {
                    for (int j = 0; j < searchResponse.ResultSets[i].Results.Length; j++)
                    {
                        for (int k = 0; k < searchResponse.ResultSets[i].Results[j].LocationData.Locations.Length; k++)
                        {
                            string name = searchResponse.ResultSets[i].Results[j].Name;
                            double lon = searchResponse.ResultSets[i].Results[j].LocationData.Locations[k].Longitude;
                            double lat = searchResponse.ResultSets[i].Results[j].LocationData.Locations[k].Latitude;
                            SmartRoute.Data.Location newLocation = new SmartRoute.Data.Location(lat, lon, name);
                            results.Add(newLocation);
    
                        }
                    }
                }
                
                return results;*/
        }



    }
}
